package com.googlecode.afx.spring;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;

import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.stage.Stage;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.core.io.ClassPathResource;

import com.googlecode.afx.AFXComponentCache;
import com.googlecode.afx.AFXMessageSource;
import com.googlecode.afx.AFXPrimaryStageHolder;
import com.googlecode.afx.annotation.AFXView;
import com.googlecode.afx.view.AbstractFxmlView;
import com.googlecode.afx.view.AbstractView;
import com.googlecode.afx.view.View;
import com.googlecode.afx.view.fxml.AbstractFxmlDatabindingView;
import com.googlecode.afx.view.fxml.FxmlPanelView;
import com.googlecode.afx.view.fxml.FxmlWindowView;
import com.googlecode.afx.view.validation.impl.ValidationHandlerImpl;

/**
 * Initializer-class for post-processing JavaFX views.
 * <p>
 * <b>Note:</b> It is required that the attribute <tt>primaryStage</tt> is set in <tt>com.googlecode.afx.AFXApplication.start(Stage stage)</tt> 
 * method, so that the view has access to the primary <tt>Stage</tt> constructed by JavaFX.
 * 
 * @author MartinKoster
 *
 */
public class AFXViewInitializer implements ApplicationContextAware, AFXComponentCache, AFXPrimaryStageHolder, AFXMessageSource {

	private ApplicationContext context;
	
	private Stage primaryStage;

	private MessageSource messageSource;
	
	private Locale locale;
	
	/**
	 * Determines which view implementation class will be used for the given
	 * <code>AFXView</code> annotation.
	 * 
	 * @param jfxView
	 * @return
	 */
	public static Class<?> resolveViewClass(AFXView jfxView) {
		if(jfxView == null) {
			return null;
		} else if("".equals(jfxView.parentId())) {
			return FxmlWindowView.class;
		} else {
			return FxmlPanelView.class;
		}
	}

	
	/**
	 * Performs a post-processing on the given view instance and initializes all internal fields. 
	 *  
	 * @param view
	 * @param controller
	 */
	public void postProcessView(AbstractView view, Object controller) {
		
		// initialize FXML related properties
		if(view instanceof AbstractFxmlView) {
			AbstractFxmlView fxmlView = (AbstractFxmlView) view;
			if(fxmlView.getFxml() != null) {
				Node fxmlNode = null;
				try {
					fxmlNode = (Node) this.load(fxmlView.getFxml(), controller);
					view.setNode(fxmlNode);
				}
				catch(IOException e) {
					throw new IllegalStateException("Can not load FXML from file '" + fxmlView.getFxml() + "'!", e);
				}
				
			}
		}
		
		// initialize databinding related properties
		if(view instanceof AbstractFxmlDatabindingView) {
			AbstractFxmlDatabindingView fxmlDatabindingView = (AbstractFxmlDatabindingView) view;
			fxmlDatabindingView.setModel(controller);
		}
		
		view.setPrimaryStageHolder(this);
		view.setViewCache(this);
		view.setMessageSource(this);
		view.setValidationHandler(new ValidationHandlerImpl(controller, view.getNode(), this));
	}
	
	/**
	 * Loads the FXML file defined in URL <tt>url</tt> and associates the <tt>controller</tt> with this view.
	 * 
	 * @param url
	 * @param controller
	 * @return
	 * @throws IOException
	 */
	protected Object load(String url, Object controller) throws IOException {
		InputStream fxmlStream = null;
		try {
			ClassPathResource resource = new ClassPathResource(url, controller.getClass());
			fxmlStream = resource.getInputStream();
			FXMLLoader loader = new FXMLLoader();
			loader.setController(controller);			
			return loader.load(fxmlStream);
		} finally {
			if (fxmlStream != null) {
				fxmlStream.close();
			}
		}
	}
	
	public void storeComponent(String id, Object component) {
		DefaultListableBeanFactory factory = (DefaultListableBeanFactory) this.context.getAutowireCapableBeanFactory();
		factory.registerSingleton(id, component);  
	}
	
	public Object lookupComponent(String id) {
		Object bean = this.context.getBean(id);
		if(bean != null && bean instanceof View) {
			return (View) bean;
		}
		return null;
	}
	
	public Stage getPrimaryStage() {
		return primaryStage;
	}

	public void setPrimaryStage(Stage primaryStage) {
		this.primaryStage = primaryStage;
	}

	@Override
	public void setApplicationContext(ApplicationContext context)
			throws BeansException {
		this.context = context;
	}

	public MessageSource getMessageSource() {
		return messageSource;
	}


	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}
	
	@Override
	public String getMessage(String code, Object[] args, String defaultMessage) {
		return this.messageSource.getMessage(code, args, defaultMessage, this.getLocale());
	}

	@Override
	public String getMessage(String code, Object[] args)
			throws NoSuchMessageException {
		return this.messageSource.getMessage(code, args, this.getLocale());
	}

	@Override
	public void setLocale(Locale locale) {
		this.locale = locale;
	}


	@Override
	public Locale getLocale() {
		return locale;
	}
	
}
